<template>
    <ul class="tree-list">
        <li v-for="item in treeData" :key="item.key" class="tree-node-group">
            <!-- 父-勾选框 -->
            <div
                v-show="typeof item.showGroup === 'boolean' ? item.showGroup : true"
                class="node-parent-wrap"
            >
                <div class="checkbox" @click="onParentCheck(item)">
                    <ElCheckbox
                        :key="`${item.key}_${checkValues.includes(item.key)}`"
                        :checked="checkValues.includes(item.key)"
                    />
                </div>
                <span class="parent-label">
                    {{ item.label }}
                </span>
                <span
                    class="toogle-child"
                    :class="{ rotate: hideChildRecord[item.key] }"
                    @click="onToogleChild(item)"
                >
                    <ElIcon size="12px" color="rgba(255, 255, 255, 0.6)">
                        <ArrowUpBold v-if="!hideChildRecord[item.key]" />
                        <ArrowDownBold v-else />
                    </ElIcon>
                </span>
            </div>
            <!-- 子-勾选 -->
            <ul
                v-if="item.children"
                v-show="!hideChildRecord[item.key]"
                class="node-child-wrap"
                :class="{
                    'hide-group-child': typeof item.showGroup === 'boolean' && !item.showGroup,
                }"
            >
                <li
                    v-for="child in item.children"
                    :key="child.label"
                    :class="{ active: checkValues.includes(child.key) }"
                    class="node-child-item"
                >
                    <div class="checkbox" @click="onChildCheck(child)">
                        <ElCheckbox
                            :key="`${child.key}_${checkValues.includes(child.key)}`"
                            :checked="checkValues.includes(child.key)"
                        />
                    </div>
                    <div v-if="typeof child.icon === 'string' && child.icon" class="node-icon">
                        <img :src="getFpiAssetsFile(child.icon)" />
                    </div>
                    <span class="child-label"> {{ child.label }}</span>
                    <span v-if="typeof child.value === 'number'" class="child-value"
                        >({{ child.value }})</span
                    >
                </li>
            </ul>
        </li>
    </ul>
</template>

<script setup lang="ts">
import { cloneDeep, pull, uniq } from 'lodash-es'
import { ArrowDownBold, ArrowUpBold } from '@element-plus/icons-vue'
import { getFpiAssetsFile } from '@/utils/tools'

// props定义
const props = defineProps<{
    treeData: {
        key: string
        label: string
        showGroup?: boolean // 配置为false时不显示分组，默认显示分组
        children: {
            key: string
            label: string
            value: number | boolean
            icon: string | boolean
        }[]
    }[]
    // 初始化时的值，组件初次加载时生效, 且一定会触发checkChange事件，所以可以将所有的处理放置在监听事件中
    defaultValue?: any[]
}>()
// emit定义
const emit = defineEmits<{
    (e: 'checkChange', parent: any, child: any): void
}>()

// 勾选的值
const checkValues = ref<any[]>(props.defaultValue ? props.defaultValue : [])

// 收集父元素被选中时的情况
const parentIsCheckMap = computed(() => {
    const map = props.treeData.reduce((a, b) => {
        if (b.children && b.children.length) {
            a[b.key] = b.children.map(c => c.key)
        }
        return a
    }, {} as any)
    return map
})

// 隐藏的子元素选中
const hideChildRecord = ref<Record<string, boolean>>({})

// 点击父级别选择器
const onParentCheck = (item: any) => {
    const childKeys =
        item.children && item.children.length ? item.children.map((c: any) => c.key) : []
    if (checkValues.value.includes(item.key)) {
        // 已经进行了勾选，需要进行移除
        const newArr = pull(checkValues.value, ...childKeys, item.key)
        checkValues.value = newArr
    } else {
        // 选中
        checkValues.value.push(...[...childKeys, item.key])
        const newArr = uniq(checkValues.value)
        checkValues.value = newArr
    }
}

// 点击子节点
const onChildCheck = (item: any) => {
    if (checkValues.value.includes(item.key)) {
        // 已经进行了勾选，需要进行移除
        const newArr = pull(checkValues.value, item.key)
        checkValues.value = newArr
    } else {
        // 选中
        checkValues.value.push(item.key)
        const newArr = uniq(checkValues.value)
        checkValues.value = newArr
    }
    resetParentKeys()
}

// 重置父节点
const resetParentKeys = () => {
    Object.keys(parentIsCheckMap.value).forEach(key => {
        const parentKey = key
        const childKeys: any[] = parentIsCheckMap.value[key]
        const isAllIn = childKeys.every(c => checkValues.value.includes(c))
        if (isAllIn) {
            checkValues.value.push(parentKey)
        } else {
            const newArr = pull(checkValues.value, parentKey)
            checkValues.value = newArr
        }
    })
}

// 默认执行一次重置
onMounted(() => {
    resetParentKeys()
})

// 对外暴露选中值
const outputCheckValues = computed(() => {
    const map = props.treeData.reduce((a, b) => {
        if (b.children && b.children.length) {
            const list: any[] = []
            b.children.forEach(c => {
                if (checkValues.value.includes(c.key)) {
                    list.push(c.key)
                }
            })
            a[b.key] = list.sort().join(',')
        } else if (checkValues.value.includes(b.key)) {
            a[b.key] = b.key
        }
        return a
    }, {} as any)
    return map
})

// 隐藏或显示子图层
const onToogleChild = (item: any) => {
    hideChildRecord.value[item.key] = !hideChildRecord.value[item.key]
}

// 执行对外暴露
watch(
    outputCheckValues,
    (newObj: any, oldObj: any) => {
        Object.keys(newObj).forEach(key => {
            const newValues = newObj ? newObj[key] : ''
            const oldValues = oldObj ? oldObj[key] : ''
            if (newValues !== oldValues) {
                emit('checkChange', key, newValues)
            }
        })
    },
    { deep: true, immediate: true, flush: 'post' },
)

watch(() => props.defaultValue, (newVal) => {
    console.log(newVal)
    if (newVal) {
        checkValues.value = newVal
    }
})

/**
 * 对外暴露此组件方法
 */
defineExpose({
    // 获取当前所有选中
    getAllCheckedKeys: () => {
        return cloneDeep(checkValues.value)
    },
    // 添加一个选中
    addValue: (value: string) => {
        if (value && !checkValues.value.includes(value)) {
            checkValues.value.push(value)
            resetParentKeys()
        }
    },
    // 移除一个选中
    removeValue: (value: string) => {
        if (value && checkValues.value.includes(value)) {
            const newArr = pull(checkValues.value, value)
            checkValues.value = newArr
            resetParentKeys()
        }
    },
    // 添加多个选中
    addValues: (values: string[]) => {
        const newValues: any[] = []
        if (values && values.length) {
            values.forEach(value => {
                if (value && !checkValues.value.includes(value)) {
                    newValues.push(value)
                }
            })
        }
        if (newValues.length) {
            checkValues.value.push(...newValues)
            resetParentKeys()
        }
    },
    // 移除多个选中
    removeValues: (values: string[]) => {
        const newValues: any[] = []
        if (values && values.length) {
            values.forEach(value => {
                if (value && checkValues.value.includes(value)) {
                    newValues.push(value)
                }
            })
        }
        if (newValues.length) {
            let cloneData = cloneDeep(checkValues.value)
            newValues.forEach(value => {
                cloneData = pull(cloneData, value)
            })
            checkValues.value = cloneData
            resetParentKeys()
        }
    },
})
</script>

<style lang="scss" scoped>
.tree-list {
    .active {
        color: #00aef6;
    }

    .checkbox {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 16px;
        height: 16px;
        margin-right: 6px;
        cursor: pointer;

        :deep(.el-checkbox) {
            --el-checkbox-input-width: 16px;
            --el-checkbox-input-height: 16px;
            --el-checkbox-height: 16px;
            --el-checkbox-bg-color: rgba(255, 255, 255, 0.08);
            --el-checkbox-input-border: 1px solid rgba(255, 255, 255, 0.3);

            .el-checkbox__inner::after {
                left: 5px;
                border-width: 2px;
            }
        }
    }

    .tree-node-group {
        margin-bottom: 1px;

        .node-parent-wrap {
            position: relative;
            display: flex;
            align-items: center;
            width: 100%;
            height: 36px;
            padding-left: 12px;
            font-size: 18px;
            font-weight: bolder;
            font-weight: normal;
            background: rgba(89, 197, 255, 0.1);

            .parent-label {
                font-size: 18px;
                font-weight: normal;
                font-weight: bolder;
                line-height: 18px;
            }

            .toogle-child {
                position: absolute;
                top: 8px;
                right: 10px;
                cursor: pointer;
                user-select: none;
            }
        }

        .node-child-wrap.hide-group-child {
            .node-child-item {
                padding-left: 12px;
                margin-bottom: 1px;
                background: rgba(89, 197, 255, 0.1);
            }
        }

        .node-child-wrap {
            .node-child-item {
                display: flex;
                align-items: center;
                justify-content: left;
                width: 100%;
                height: 36px;
                padding-left: 20px;

                .child-label {
                    font-size: 18px;
                    font-weight: normal;
                    line-height: 18px;
                }

                .child-value {
                    font-size: 18px;
                    font-weight: normal;
                    line-height: 18px;
                    margin-left: 4px;
                }

                .node-icon {
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    width: 24px;
                    height: 24px;
                    margin-right: 6px;
                    background: rgba(255, 255, 255, 0.1);
                    border-radius: 10px;

                    > img {
                        width: 18px;
                        height: 18px;
                    }
                }
            }
        }
    }
}
</style>
